package com.pinfly.common.util;

import javax.servlet.http.HttpServletRequest;

/**
 * Class to encapsulate information about the user agent of an http request.
 * This class can be used to determine the browser type and version as well as
 * the OS type and version.
 */
public class UserAgent
{
    /** Static for identifying Internet Explorer browser */
    public static final Browser IE = new Browser ("IE");
    /** Static for identifying IE version 5.0 */
    public static final Version IE5_0 = new Version (IE, "5.0", 1);
    /** Static for identifying IE version 5.5 */
    public static final Version IE5_5 = new Version (IE, "5.5", 2);
    /** Static for identifying IE version 6.0 */
    public static final Version IE6_0 = new Version (IE, "6.0", 3);
    /** Static for identifying IE version 7.0 */
    public static final Version IE7_0 = new Version (IE, "7.0", 4);
    /** Static for identifying IE version 8.0 */
    public static final Version IE8_0 = new Version (IE, "8.0", 5);

    /** Static for identifying Netscape browser */
    public static final Browser Netscape = new Browser ("Netscape");
    /** Static for identifying Netscape versions 4.x */
    public static final Version NS4x = new Version (Netscape, "4.x", 1);
    /** Static for identifying Netscape versions 6.x */
    public static final Version NS6x = new Version (Netscape, "6.x", 2);
    /** Static for identifying Netscape versions 7.x */
    public static final Version NS7x = new Version (Netscape, "7.x", 3);

    /** Static for identifying Firefox browser */
    public static final Browser Firefox = new Browser ("Firefox");
    /** Static for identifying Firefox version 1.5 */
    public static final Version FF1_5 = new Version (Firefox, "1.5", 15);
    /** Static for identifying Firefox version 2.0 */
    public static final Version FF2_0 = new Version (Firefox, "2.0", 20);
    /** Static for identifying Firefox version 3.6.x */
    public static final Version FF3_6 = new Version (Firefox, "3.6", 36);

    /** Static for identifying Windows as the operating system */
    public static final OS Windows = new OS ("Windows");
    /** Static for identifying Windows version as Windows 95 */
    public static final Version Win95 = new Version (Windows, "95", 1);
    /** Static for identifying Windows version as Windows 98 */
    public static final Version Win98 = new Version (Windows, "98", 2);
    /** Static for identifying Windows version as Windows ME */
    public static final Version WinME = new Version (Windows, "ME", 3);
    /** Static for identifying Windows version as Windows NT 4 */
    public static final Version WinNT = new Version (Windows, "NT", 4);
    /** Static for identifying Windows version as Windows 2000 */
    public static final Version Win2K = new Version (Windows, "2000", 5);
    /** Static for identifying Windows version as Windows XP */
    public static final Version WinXP = new Version (Windows, "XP", 6);
    /** Static for identifying Windows version as Windows 2003 */
    public static final Version Win2003 = new Version (Windows, "2003", 7);
    /** Static for identifying Windows version as Vista */
    public static final Version Vista = new Version (Windows, "Vista", 8);
    /** Static for identifying Windows version as Windows 7 or 2008 */
    public static final Version Win7or2008 = new Version (Windows, "7or2008", 9);

    /** Static for identifying Solaris as the operating system */
    public static final OS Solaris = new OS ("Solaris");
    /** Static for identifying Solaris as version 6 */
    public static final Version Solaris6 = new Version (Solaris, "6", 1);
    /** Static for identifying Solaris as version 6 */
    public static final Version Solaris7 = new Version (Solaris, "7", 2);
    /** Static for identifying Solaris as version 6 */
    public static final Version Solaris8 = new Version (Solaris, "8", 3);
    /** Static for identifying Solaris as version 6 */
    public static final Version Solaris9 = new Version (Solaris, "9", 4);

    private Browser m_browser;
    private Version m_browserVersion;
    private OS m_os;
    private Version m_osVersion;

    /**
     * Factory method to create the <code>UserAgent</code> for an http request.
     * The only supported combinations are Windows 95 or later with Netscape 6.0
     * or later or IE 5.0 or later; or Solaris 6 or later with Netscape 4.x. Any
     * other combination will throw an IllegalArgumentException.
     * 
     * @param req the <code>HttpServletRequest</code> to determine the user
     *            agent for
     * @return a <code>UserAgent</code> as specified in the request
     * @throws IllegalArgumentException if the request specifies an unsupported
     *             user agent
     */
    public static UserAgent getUserAgent (HttpServletRequest req)
    {
        Browser browser = null;
        Version browserVersion = null;
        OS os = null;
        Version osVersion = null;

        String userAgent = req.getHeader ("User-Agent").toLowerCase ();

        if (userAgent.indexOf ("windows") != -1)
        {
            os = Windows;
            if (userAgent.indexOf ("win95") != -1 || userAgent.indexOf ("windows 95") != -1)
            {
                osVersion = Win95;
            }
            else if (userAgent.indexOf ("win98") != -1 || userAgent.indexOf ("windows 98") != -1)
            {
                osVersion = Win98;
            }
            else if (userAgent.indexOf ("win 9x 4.90") != -1)
            {
                osVersion = WinME;
            }
            else if (userAgent.indexOf ("windows nt 4") != -1)
            {
                osVersion = WinNT;
            }
            else if (userAgent.indexOf ("windows nt 5.0") != -1)
            {
                osVersion = Win2K;
            }
            else if (userAgent.indexOf ("windows nt 5.1") != -1)
            {
                osVersion = WinXP;
            }
            else if (userAgent.indexOf ("windows nt 5.2") != -1)
            {
                osVersion = Win2003;
            }
            else if (userAgent.indexOf ("windows nt 6.0") != -1)
            {
                osVersion = Vista;
            }
            else if (userAgent.indexOf ("windows nt 6.1") != -1)
            {
                osVersion = Win7or2008;
            }
        }
        else if (userAgent.indexOf ("sunos") != -1)
        {
            os = Solaris;
            if (userAgent.indexOf ("sunos 5.6") != -1)
            {
                osVersion = Solaris6;
            }
            else if (userAgent.indexOf ("sunos 5.7") != -1)
            {
                osVersion = Solaris7;
            }
            else if (userAgent.indexOf ("sunos 5.8") != -1)
            {
                osVersion = Solaris8;
            }
            else if (userAgent.indexOf ("sunos 5.9") != -1)
            {
                osVersion = Solaris9;
            }
        }

        if (userAgent.indexOf ("msie") != -1)
        {
            browser = IE;
            if (userAgent.indexOf ("msie 5.0") != -1)
            {
                browserVersion = IE5_0;
            }
            else if (userAgent.indexOf ("msie 5.5") != -1)
            {
                browserVersion = IE5_5;
            }
            else if (userAgent.indexOf ("msie 6.0") != -1)
            {
                browserVersion = IE6_0;
            }
            else if (userAgent.indexOf ("msie 7.0") != -1)
            {
                browserVersion = IE7_0;
            }
            else if (userAgent.indexOf ("msie 8.0") != -1)
            {
                browserVersion = IE8_0;
            }
        }
        else if (userAgent.indexOf ("firefox") != -1)
        {
            browser = Firefox;
            if (userAgent.indexOf ("firefox/1.5") != -1)
            {
                browserVersion = FF1_5;
            }
            else if (userAgent.indexOf ("firefox/2.0") != -1)
            {
                browserVersion = FF2_0;
            }
            else if (userAgent.indexOf ("firefox/3.6") != -1)
            {
                browserVersion = FF3_6;
            }
        }
        else if (userAgent.indexOf ("netscape") != -1)
        {
            browser = Netscape;
            if (userAgent.indexOf ("netscape6") != -1 || userAgent.indexOf ("netscape/6") != -1)
            {
                browserVersion = NS6x;
            }
            else if (userAgent.indexOf ("netscape/7") != -1)
            {
                browserVersion = NS7x;
            }
        }
        else if (os == Solaris && userAgent.indexOf ("mozilla/4") != -1)
        {
            browser = Netscape;
            browserVersion = NS4x;
        }

        // One final thing. Netscape 6 on Solaris doesn't include
        // the Solaris version in the user agent string. It has to be
        // Solaris 7 or later, so we'll just use that.
        if (browserVersion == NS6x && os == Solaris && osVersion == null)
        {
            osVersion = Solaris7;
        }

        if (browser == null || browserVersion == null || os == null || osVersion == null)
        {
            throw new IllegalArgumentException ("Unknown user agent: " + userAgent);
        }

        return new UserAgent (browser, browserVersion, os, osVersion);
    }

    private UserAgent (Browser browser, Version browserVersion, OS os, Version osVersion)
    {
        m_browser = browser;
        m_browserVersion = browserVersion;
        m_os = os;
        m_osVersion = osVersion;
    }

    /**
     * Does the user agent have the specified browser?
     * 
     * @param b the <code>Browser</code> to test for
     * @return <code>true</code> if the user agent has the specified browser
     */
    public boolean isBrowser (Browser b)
    {
        return m_browser == b;
    }

    /**
     * Is the browser in the user agent at the specified version?
     * 
     * @param v The <code>Version</code> to test for.
     * @return <code>true</code> if the browser is of the same type as specified
     *         in <code>v</code> and is the same version.
     */
    public boolean isBrowserVersion (Version v)
    {
        return m_browserVersion == v;
    }

    /**
     * Is the browser in the user agent at or later than the specified version?
     * 
     * @param v The <code>Version</code> to test for.
     * @return <code>true</code> if the browser of the same type as specified in
     *         <code>v</code> and is at or later than the specified version.
     */
    public boolean isBrowserAtOrLater (Version v)
    {
        if (v == null)
        {
            throw new IllegalArgumentException ("Version can't be null");
        }
        return m_browserVersion.m_type == v.m_type && m_browserVersion.isAtOrLater (v);
    }

    /**
     * Does the user agent have the specified operating system?
     * 
     * @param o The <code>OperatingSystem</code> to test for.
     * @return <code>true</code> if the user agent has the specified operating
     *         system.
     */
    public boolean isOS (OS o)
    {
        return m_os == o;
    }

    /**
     * Is the operating system in the user agent at the specified version?
     * 
     * @param v The <code>Version</code> to test for.
     * @return <code>true</code> if the operating system is the same type as
     *         specified in <code>v</code> and is the specified version.
     */
    public boolean isOSVersion (Version v)
    {
        return m_osVersion == v;
    }

    /**
     * Is the operating system in the user agent at or later than the specified
     * version?
     * 
     * @param v The <code>Version</code> to test for.
     * @return <code>true</code> if the operating system is the same type as
     *         specified in <code>v</code> and is at or later than the specified
     *         version.
     */
    public boolean isOSAtOrLater (Version v)
    {
        if (v == null)
        {
            throw new IllegalArgumentException ("Version can't be null");
        }
        return m_osVersion.m_type == v.m_type && m_osVersion.isAtOrLater (v);
    }

    /**
     * Get a printable version of the user agent. This consists of the browser
     * name and version, followed by the literal "running on" and the operating
     * system name and version.
     */
    public String toString ()
    {
        return m_browser.toString () + ' ' + m_browserVersion.toString () + " running on " + m_os.toString () + ' '
               + m_osVersion.toString ();
    }

    private static class Item
    {
        private String m_type;

        private Item (String type)
        {
            m_type = type;
        }

        public String toString ()
        {
            return m_type;
        }
    }

    /**
     * Class to define browser types. There is a static instance of this class
     * for each supported browser.
     */
    private static class Browser extends Item
    {
        private Browser (String type)
        {
            super (type);
        }
    }

    /**
     * Class to define OS types. There is a static instance of this class for
     * each supported OS.
     */
    private static class OS extends Item
    {
        private OS (String type)
        {
            super (type);
        }
    }

    /**
     * Class to define versions. There is a static instance of this class to
     * define each supported version
     */
    private static class Version
    {
        private Item m_type;
        private String m_label;
        private int m_version;

        Version (Item type, String label, int ver)
        {
            m_type = type;
            m_label = label;
            m_version = ver;
        }

        /**
         * Determine if this version is at or later than another version. The
         * other verions must not be null and must be of the same type as this
         * version.
         */
        boolean isAtOrLater (Version v)
        {
            if (v == null)
            {
                throw new IllegalArgumentException ("Cannot compare with null version");
            }

            if (v.m_type != m_type)
            {
                throw new IllegalArgumentException ("Cannot compare " + m_type + " version with " + v.m_type);
            }

            return m_version >= v.m_version;
        }

        public String toString ()
        {
            return m_label;
        }
    }

}
